%% fast_transform.erl
%%      Gives general interface for changing a set of nodes in an abstract syntax tree (AST), according to given rules.
%%      Gives pre-defined rules to rename all the variables in an abstract syntax tree fragment.
%%
%% Copyright (C) 2008, 2009 Gábor Fehér
%%
%% This file is part of ECT.
%% 
%% ECT is free software: you can redistribute it and/or modify
%% it under the terms of the GNU General Public License as published by
%% the Free Software Foundation, either version 3 of the License, or
%% (at your option) any later version.
%% 
%% ECT is distributed in the hope that it will be useful,
%% but WITHOUT ANY WARRANTY; without even the implied warranty of
%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
%% GNU General Public License for more details.
%% 
%% You should have received a copy of the GNU General Public License
%% along with ECT.  If not, see <http://www.gnu.org/licenses/>.
%%
-module(fast_transform).
-export([transform/2, rename_variables/2]).
-record(info, {substitution_list}).

%% Function: transform(Substitutions, Nodes)
%% Purpose: traverses an abstract syntax tree, and transforms each node with a given function
%% Arguments:
%%   Substitutions: a list of funs to transform the tree
%%                  Its form is the following: [{Pos, Name, Fun}]. For each node in the tree
%%                  the first matching item from this list is located, and it is applied to it.
%%                  Those nodes that do not match any item, are left intact.
%%                  * Pos and Name define the set of matching nodes. A node XX matches, if 
%%                  element(Pos, XX) =:= Name.
%%                  * Fun is a fun, and it takes the matching node as an argument, and transforms it.
%%   Nodes: the abstract syntax tree to transform
%% Returns:
%%   The transformed version of Nodes, transformed according to Substitutions.
transform(Substitutions, Nodes) ->
    traverse(#info{substitution_list = Substitutions}, Nodes).

%% Function: subst(Substitutions, Node)
%% Purpose: applies a list of substitutions to an AST node
%% Arguments:
%%   Substitutions::[{integer(), any(), fun()}], interpreted according to the head comments of transform/2.
%%   Node: an abstract syntax tree node
%% Returns:
%%   Node transformed according to substitutions.
subst([], Tuple) ->
    Tuple;
subst([{Pos, OldName, Fun}|_], Tuple) when element(Pos, Tuple) =:= OldName ->
    Fun(Tuple);
subst([_|List], Tuple) ->
    subst(List, Tuple).

%% Function: substitute(SubstInfo, Node)
%% Purpose: applies a list of substitutions to an AST node
%% Arguments:
%%   SubstInfo::#info{}, where SubstInfo#info.substitution_list is interpreted according to the head comments of transform/2
%%   Node: an abstract syntax tree node
%% Returns:
%%   Node transformed according to the substitutions defined in SubstInfo.
substitute(Info = #info{substitution_list = Substitutions}, Tuple) ->
    subst(Substitutions, Tuple).

%% Function: substitute(SubstInfo, Node)
%% Purpose: applies a list of substitutions to an AST node, and all its child nodes
%% Arguments:
%%   SubstInfo::#info{}, where SubstInfo#info.substitution_list is interpreted according to the head comments of transform/2
%%   Node: an abstract syntax tree node
%% Returns:
%%   Node and its child nodes transformed recursively, according to the substitutions defined in SubstInfo.
traverse(Info, Tuple1) when is_tuple(Tuple1) ->
    Tuple2 = substitute(Info, Tuple1),

    [Name,Line|List1] = tuple_to_list(Tuple2),
    List2 = traverse(Info, List1),
    list_to_tuple([Name,Line|List2]);
traverse(Info, List) when is_list(List) ->
    lists:map(fun (Item) -> traverse(Info, Item) end, List);
traverse(Info, Node) ->
    Node.


%% Function: rename_variables(Renames, Node)
%% Purpose: renames all the variables in an AST fragment
%% Arguments:
%%   Renames = [{OldName, NewName}, ...] - list of pairs that define variable name changes from old names to new names
%%   Node: an abstract syntax tree node
%% Returns:
%%   Node, but with all its variable sub-nodes (including itself) renamed according to Renames.
%%   (If a variable name is not specified in Renames, then those variables will not be affected and keep their names.)
rename_variables(Renames, Node) ->
    Substitutions =
        lists:map(
            fun ({OldName, NewName}) ->
                Fun = 
                    fun ({var, Line, OldName0}) when OldName0 =:= OldName ->
                            {var, Line, NewName};
                        ({_, _, OldName0} = LuckyItem) when OldName0 =:= OldName ->
                            LuckyItem
                    end,
                {3, OldName, Fun}
            end,Renames),
    Node2 = transform(Substitutions, Node),
    Node2.
